package hms.payment;

import hms.database.DBRowObject;

import java.awt.Component;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.MaskFormatter;


public class PaymentProcessor {
	
	private int guestID;
	private double remainingBalance;
	private ArrayList<MoneyOption> queuedPayments;
	private ArrayList<PaymentMethod> queuedMethods;
	private JPanel panel;
	private JFrame frame;
	private static final String[] methods = {"Choose a method","Cash", "Check", "Credit Card", "Debit Card"};
	private JComboBox paymentSelector;
	private JTextField amountField;
	private JFormattedTextField accountNumber;
	private JFormattedTextField routingNumber;
	private JTextField bankName;
	private JFormattedTextField cardNumber;
	private JTextField cvv2;
	private JComboBox expMonth;
	private JComboBox expYear;
	private JLabel cardType;
	private String cardTypestr;
	private boolean validCard;
	
	private static final String[] months = {"Janurary", "February", "March", "April", "May", "June",
			"July","August","September","October","November","December"};
	
	
	public PaymentProcessor(int guestID, double remainingBalance) {
		this.guestID = guestID;
		this.remainingBalance = remainingBalance;

		queuedPayments = new ArrayList<MoneyOption>();
		queuedMethods = new ArrayList<PaymentMethod>();
	}
	
	/**
	 * Get total of pending credits to be applied to a customer's account.
	 * 
	 * @return
	 */
	public double retrievePendingCredits() {
		double sum = 0;
		for(MoneyOption m : queuedPayments) {
			sum += m.getAmount();
		}
		return sum;
	}
	
	public void setRemainingBalance(double balance) {
		remainingBalance = balance;
	}
	
	/**
	 * Pop up a window to type in new payment information.
	 * 
	 * @return
	 */
	public double newPaymentPopup() {
		frame = new JFrame();
		this.panel = new JPanel();
		paymentSelector = new JComboBox(methods);
		JLabel select =  new JLabel("Select a payment method above");
		panel.add(select);
		Object[] layout = {paymentSelector, panel};
		Object[] options = {"OK","Cancel"};

		int response = JOptionPane.showOptionDialog(frame,
				layout,
				"Select Payment Method",
				JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.PLAIN_MESSAGE,
				null,
				options,
				null);
		if(response != JOptionPane.OK_OPTION || paymentSelector.getSelectedItem().toString()
				.equals(methods[0])) {
			return 0;
		}
		
		
		String title = null;
		// Now the payment type has been selected
		if (paymentSelector.getSelectedItem().toString().equals(methods[0])) {
			panel = new JPanel();
			panel.add(new JLabel("Select a payment method above"));
		} else if (paymentSelector.getSelectedItem().toString()
				.equals(methods[1])) {
			panel = cashFrame();
			title = "Pay with Cash";
		} else if (paymentSelector.getSelectedItem().toString()
				.equals(methods[2])) {
			panel = checkFrame();
			title = "Pay with Check";
		} else if (paymentSelector.getSelectedItem().toString()
				.equals(methods[3])) {
			panel = creditFrame();
			title = "Pay with Credit Card";
		} else if (paymentSelector.getSelectedItem().toString()
				.equals(methods[4])) {
			panel = debitFrame();
			title = "Pay with Debit Card";
		}
		
		 response = JOptionPane.showOptionDialog(frame,
				panel,
				title,
				JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.PLAIN_MESSAGE,
				null,
				options,
				null);
		
		
		if(response == JOptionPane.OK_OPTION) {
			// Create Payment Objects
			double amount = Double.parseDouble("0"+amountField.getText().replaceAll("[^0-9\\.]", ""));
			if (paymentSelector.getSelectedItem().toString()
					.equals(methods[1])) {
				// Cash
				if(amount > 0 && amount <= remainingBalance) {
					PaymentMethod method = new PaymentMethod(guestID);
					CashOption payment = new CashOption();
					method.setCashSelected(true);
					payment.setCashAmount(amount);
					queuedMethods.add(method);
					queuedPayments.add(payment);
				} else {
					JOptionPane.showMessageDialog(null, "Invalid input!\nMake sure all fields are filled in and\namount charged is not in excess of balance or zero.", "Payment not validated", JOptionPane.ERROR_MESSAGE);
				}
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[2])) {
				// Check
				if(amount > 0 && amount <= remainingBalance &&
						accountNumber.getText().length() == 9 &&
						routingNumber.getText().length() == 13 &&
						bankName.getText().length() > 0 &&
						bankName.getText().length() < 128) {
					PaymentMethod method = new PaymentMethod(guestID);
					CheckOption payment = new CheckOption();
					payment.setAccountNumber(accountNumber.getText().substring(0, 8));
					payment.setRoutingNumber(routingNumber.getText().substring(0,12));
					payment.setBankName(bankName.getText());
					payment.setCheckAmount(amount);
					queuedMethods.add(method);
					queuedPayments.add(payment);
				} else {
					JOptionPane.showMessageDialog(null, "Invalid input!\nBank Name cannot exceed 127 characters\nMake sure all fields are filled in and\namount charged is not in excess of balance or zero.", "Payment not validated", JOptionPane.ERROR_MESSAGE);
				}
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[3])) {
				// Credit
				cvv2.setText(cvv2.getText().replaceAll("[^0-9]",""));
				if(amount > 0 && amount <= remainingBalance &&
						validCard &&
						cvv2.getText().length() >= 3 &&
						cvv2.getText().length() <= 4
						) {
					PaymentMethod method = new PaymentMethod(guestID);
					CreditOption payment = new CreditOption();
					if(cardTypestr.length() > 15) {
						cardTypestr = cardTypestr.substring(0, 15);
					}
					payment.setCardDistributor(cardTypestr);
					payment.setCardNumber(cardNumber.getText().replaceAll("[^0-9]", ""));
					payment.setCVV2(cvv2.getText());
					payment.setExpirationDate(expMonth.getSelectedItem().toString(), expYear.getSelectedItem().toString());
					payment.setCreditAmount(amount);
					queuedMethods.add(method);
					queuedPayments.add(payment);
					
				} else {
					JOptionPane.showMessageDialog(null, "Invalid input!\nMake sure all fields are filled in and\namount charged is not in excess of balance or zero.", "Payment not validated", JOptionPane.ERROR_MESSAGE);
				}
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[4])) {
				cvv2.setText(cvv2.getText().replaceAll("[^0-9]",""));
				// Debit
				if(amount > 0 && amount <= remainingBalance &&
						validCard &&
						cvv2.getText().length() >= 3 &&
						cvv2.getText().length() <= 4 &&
						bankName.getText().length() > 0 &&
						bankName.getText().length() < 128
						) {
					PaymentMethod method = new PaymentMethod(guestID);
					DebitOption payment = new DebitOption();
					if(cardTypestr.length() > 15) {
						cardTypestr = cardTypestr.substring(0, 15);
					}
					payment.setCardDistributor(cardTypestr);
					payment.setCardNumber(cardNumber.getText().replaceAll("[^0-9]", ""));
					payment.setCVV2(cvv2.getText());
					payment.setExpirationDate(expMonth.getSelectedItem().toString(), expYear.getSelectedItem().toString());
					payment.setBankName(bankName.getText());
					payment.setDebitAmount(amount);
					queuedMethods.add(method);
					queuedPayments.add(payment);
				} else {
					JOptionPane.showMessageDialog(null, "Invalid input!\nBank Name cannot exceed 127 characters\nMake sure all fields are filled in and\namount charged is not in excess of balance or zero.", "Payment not validated", JOptionPane.ERROR_MESSAGE);
				}
			}
		}
		return 0;
	}
	
	/**
	 * Commit all pending transactions to the database.
	 */
	public void commitToDatabase() {
		for(int i = 0;i<queuedMethods.size();i++) {
			DBRowObject method = queuedMethods.get(i);
			method.commitToDatabase();
			MoneyOption mo = queuedPayments.get(i);
			mo.setPaymentMethodID(method.getUniqueIdentifier());
			mo.commitToDatabase();
		}
		queuedMethods.clear();
		queuedPayments.clear();
	}
	
	private String issuerIdentifier(String cardNumber) {
		// Remove non-numbers
		cardNumber = cardNumber.replaceAll("[^0-9]", "");
		
		int sum = 0;
		boolean isOdd = true;
		
		// Verify card number
		for(char c : cardNumber.toCharArray()) {
			int i = Character.getNumericValue(c);
			if(isOdd) {
				if(2*i >= 10) {
					sum += 2*i-9;
				} else {
				sum += 2*i;
				}
			} else {
				sum += i;
			}
			isOdd = !isOdd;
		}
		
		if(sum%10 == 0) {
			// Detect issuer
			if(cardNumber.matches("3[47]..............$")) // AMEX
			{
				validCard = true;
				return "American Express";
			}
			else if(cardNumber.matches("4...........(....|.)$")) // VISA
			{
				validCard = true;
				return "Visa";
			}
			else if(cardNumber.matches("5[1-5]..............$")) // MasterCard
			{
				validCard = true;
				return "MasterCard";
			}
			else if(cardNumber.matches("6011............$")) // Discover
			{
				validCard = true;
				return "Discover";
			} else {
				// Invalid issuer
				validCard = false;
				return "Issuer not valid";
			}
		} else {
			// Invalid card number
			validCard = false;
			return "Invalid credit card number";
		}
	}
	
	private class PaymentSelect implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {
			frame.remove(panel);
			Component[] c = frame.getComponents();
			if (paymentSelector.getSelectedItem().toString().equals(methods[0])) {
				panel = new JPanel();
				panel.add(new JLabel("Select a payment method above"));
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[1])) {
				panel = cashFrame();
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[2])) {
				panel = checkFrame();
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[3])) {
				panel = creditFrame();
			} else if (paymentSelector.getSelectedItem().toString()
					.equals(methods[4])) {
				panel = debitFrame();
			}
			panel.setVisible(true);
		}
		
	}
	
	private class CardValidator implements DocumentListener {

		@Override
		public void changedUpdate(DocumentEvent e) {
			
		}

		@Override
		public void insertUpdate(DocumentEvent e) {
			cardTypestr = issuerIdentifier(cardNumber.getText());
			cardType.setText("Card Type: " + cardTypestr);
		}

		@Override
		public void removeUpdate(DocumentEvent e) {
			cardTypestr = issuerIdentifier(cardNumber.getText());
			cardType.setText("Card Type: " + cardTypestr);
			
		}
	}
	
	/**
	 * Generate a new frame for inputting cash transactions.
	 * 
	 * @return Frame containing fields for inputting cash 
	 */
	private JPanel cashFrame() {
		JPanel frame = new JPanel();
		frame.setLayout(new BoxLayout(frame, BoxLayout.PAGE_AXIS));
		JLabel cashLabel = new JLabel("Cash amount received:");
		frame.add(cashLabel);
		amountField = new JTextField();
		amountField.setText(String.format("%.2f", remainingBalance));
		frame.add(amountField);
		return frame;
	}
	

	private JPanel checkFrame() {
		JPanel frame = new JPanel();
		frame.setLayout(new BoxLayout(frame, BoxLayout.PAGE_AXIS));
		try {
			MaskFormatter accountFormatter = new MaskFormatter("#########");
			MaskFormatter routingFormatter = new MaskFormatter("#############");
			
			accountNumber = new JFormattedTextField(accountFormatter);
			routingNumber = new JFormattedTextField(routingFormatter);
			bankName = new JTextField();
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		frame.add(new JLabel("Account Number (9 digits):"));
		frame.add(accountNumber);
		
		frame.add(new JLabel("Routing Number (13 digits):"));
		frame.add(routingNumber);
		
		frame.add(new JLabel("Bank Name:"));
		frame.add(bankName);

		frame.add(new JLabel("Check amount:"));
		amountField = new JTextField();
		amountField.setText(String.format("%.2f", remainingBalance));
		frame.add(amountField);
		return frame;

	}
	

	private JPanel creditFrame() {
		validCard = false;
		JPanel frame = new JPanel();
		frame.setLayout(new BoxLayout(frame, BoxLayout.PAGE_AXIS));

		try {
			MaskFormatter cardFormatter = new MaskFormatter("#### #### #### ####");
			MaskFormatter cvv2Formatter = new MaskFormatter("####");
			
			cardNumber = new JFormattedTextField(cardFormatter);
			cvv2 = new JTextField();
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		expMonth = new JComboBox(months);
		Calendar c = new GregorianCalendar();
		int currentYear = c.get(Calendar.YEAR);
		String[] years = { String.valueOf(currentYear),
				String.valueOf(currentYear+1),
				String.valueOf(currentYear+2),
				String.valueOf(currentYear+3),
				String.valueOf(currentYear+4),
				String.valueOf(currentYear+5),
				String.valueOf(currentYear+6),
				String.valueOf(currentYear+7)};
		expYear = new JComboBox(years);
		
		frame.add(new JLabel("Card Number:"));
		frame.add(cardNumber);
		
		cardType = new JLabel("Card Issuer: ");
		cardTypestr = "";
		frame.add(cardType);
		
		frame.add(new JLabel("CVV2:"));
		frame.add(cvv2);
		
		frame.add(new JLabel("Expiration Date:"));
		frame.add(expMonth);
		frame.add(expYear);
		
		frame.add(new JLabel("Credit amount:"));
		amountField = new JTextField();
		amountField.setText(String.format("%.2f", remainingBalance));
		frame.add(amountField);
		
		cardNumber.getDocument().addDocumentListener(new CardValidator());
		return frame;
	}
	

	private JPanel debitFrame() {
		validCard = false;
		JPanel frame = new JPanel();
		BoxLayout b = new BoxLayout(frame, BoxLayout.PAGE_AXIS);
		frame.setLayout(b);

		try {
			MaskFormatter cardFormatter = new MaskFormatter("#### #### #### ####");
			MaskFormatter cvv2Formatter = new MaskFormatter("####");
			
			cardNumber = new JFormattedTextField(cardFormatter);
			cvv2 = new JFormattedTextField(cvv2Formatter);
			bankName = new JTextField();
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		expMonth = new JComboBox(months);
		Calendar c = new GregorianCalendar();
		int currentYear = c.get(Calendar.YEAR);
		String[] years = { String.valueOf(currentYear),
				String.valueOf(currentYear+1),
				String.valueOf(currentYear+2),
				String.valueOf(currentYear+3),
				String.valueOf(currentYear+4),
				String.valueOf(currentYear+5),
				String.valueOf(currentYear+6),
				String.valueOf(currentYear+7)};
		expYear = new JComboBox(years);
		
		frame.add(new JLabel("Card Number:"));
		frame.add(cardNumber);
		
		cardType = new JLabel("Card Issuer: ");
		cardTypestr = "";
		frame.add(cardType);
		
		frame.add(new JLabel("CVV2:"));
		frame.add(cvv2);
		
		frame.add(new JLabel("Expiration Date:"));
		frame.add(expMonth);
		frame.add(expYear);
		
		frame.add(new JLabel("Bank Name:"));
		frame.add(bankName);

		frame.add(new JLabel("Debit amount:"));
		amountField = new JTextField();
		amountField.setText(String.format("%.2f", remainingBalance));
		frame.add(amountField);
		
		cardNumber.getDocument().addDocumentListener(new CardValidator());
		return frame;
	}

	/*
	public static void main(String[] argv) {
		PaymentProcessor p = new PaymentProcessor(1, 50);
		p.newPaymentPopup();
		System.out.println(p.retrievePendingCredits());
		p.commitToDatabase();
		System.exit(0);
	}*/
	
	
}
